科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件浅议C++/CLI的gcnew关键字

浅议C++/CLI的gcnew关键字

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换*。

作者:LEAK 来源:C++/CLI社区 2007年11月16日

关键字: C++ gcnew 关键字

  • 评论
  • 分享微博
  • 分享邮件
 C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:

  1.     gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址.
  2.     gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放.
 
  当然,从程序员的角度来说,管它是句柄还是什么其他的东西,总跑不掉是对某块内存地址的引用,实际上我们都可以理解成指针.下面我们就写一段代码来测试一下好了.
 
using namespace System;
 
ref class Foo
{
public:
    Foo()
    {
       System::Console::WriteLine("Foo::Foo");
    }
    ~Foo()
    {
       System::Console::WriteLine("Foo::~Foo");
    }
public:
    int m_iValue;
};
 
int _tmain()
{
    int* pInt = new int;
    int^ rInt = gcnew int;
    Foo^ rFoo = gcnew Foo;
 
    delete rFoo;
    delete rInt;
    delete pInt;
}
 
  我把调试的时候JIT编译的汇编代码择录了部分如下显示(请注意红色部分):

    int* pInt = new int;
0000004c  mov         ecx,4
00000051  call        dword ptr ds:[03B51554h]
00000057  mov         esi,eax
00000059  mov         dword ptr [esp+18h],esi
    int^ rInt = gcnew int;
0000005d  mov         ecx,788EF9D8h
00000062  call        FCFAF66C
00000067  mov         esi,eax
00000069  mov         dword ptr [esi+4],0
00000070  mov         edi,esi
    Foo^ rFoo = gcnew Foo;
00000072  mov         ecx,3B51768h
00000077  call        FCFAF66C
0000007c  mov         esi,eax
0000007e  mov         ecx,esi
00000080  call        dword ptr ds:[03B517ACh]
00000086  mov         dword ptr [esp+1Ch],esi
 
    delete rFoo;
0000008a  mov         ebx,dword ptr [esp+1Ch]
0000008e  test        ebx,ebx
00000090  je          000000A4
00000092  mov         ecx,ebx
00000094  call        dword ptr ds:[03FD0028h]
0000009a  mov         dword ptr [esp+14h],0
000000a2  jmp         000000AC
000000a4  mov         dword ptr [esp+14h],0
    delete rInt;
000000ac  mov         edx,edi
000000ae  mov         ecx,788F747Ch
000000b3  call        FC8D20FD
000000b8  mov         ebp,eax
000000ba  test        ebp,ebp
000000bc  je          000000D0
000000be  mov         ecx,ebp
000000c0  call        dword ptr ds:[03FD0020h]
000000c6  mov         dword ptr [esp+10h],0
000000ce  jmp         000000D8
000000d0  mov         dword ptr [esp+10h],0
    delete pInt;
000000d8  mov         ecx,dword ptr [esp+18h]
000000dc  call        dword ptr ds:[03B51540h]
 
 
   我们先看分配内存这部分的代码
 
  1.调用new方式分配
int* pInt = new int;
0000004c  mov         ecx,4
00000051  call        dword ptr ds:[03B51554h]

  可以看到,和以前在vc6中一样,分配内存的步骤如下:
  1.  首先把sizeof(int) = 4 放到ecx中
  2.  调用operator new 去分配4个字节
  3.  调用构造函数等等......(这里不是我们的重点)

  成功分配后,会把返回地址放在eax中。
 
  2.调用gcnew方式分配
    int^ rInt = gcnew int;
0000005d  mov         ecx,788EF9D8h
00000062  call        FCFAF66C
。。。
    Foo^ rFoo = gcnew Foo;
00000072  mov         ecx,3B51768h
00000077  call        FCFAF66C

  可以看到gcnew也是通过把一个参数放到ecx中,然后再调用一个函数来完成分配的操作,显然0x788EF9D8应该是一个地址,而不可能是一个数值。我们可以看到这里gcnew创建两个不同类型的变量,调用的函数地址却都是0xFCFAF66C,而存放到ecx中的两个地址就不一样。究竟这几个地址代表什么呢?
 
  和new一样gcnew也是把返回地址放在eax中。我们直接从内存窗口看eax指向的内存块好了。Aha,看到了没有?

  这次的eax = 0x00F73404  对应的内存块为
 
0x00F73404  d8 f9 8e 78 00 00 00 00 。。。
 
  这个不就是 mov 到 ecx中的值么?再回忆昨天写的分析Object对象布局的文章,可以肯定这个就是 MethodTable地址了,对于这个int来说,后面的4个字节对应的就是存放它的RawData,比如如果你初始化为 4 那么内存对应的就变化为 d8 f9 8e 79 04 00 00 00
 
  分析清楚存放到ecx中的是 MethodTable指针,我们再分析那个对应的call函数,从vm的代码可以看出,有三个全局函数用来根据MethodTable创建对象,同时MethodTable本身也提供一个成员函数Allocate(),只不过这个成员函数也是调用的下面的函数:

OBJECTREF AllocateObject( MethodTable *pMT )
OBJECTREF AllocateObjectSpecial( MethodTable *pMT )
OBJECTREF FastAllocateObject( MethodTable *pMT )
 
  其中AllocateObject又是调用AllocateObjectSpecial来完成工作。那么我们调用的应该就是AllocateObject或者FastAllocateObject了。

  在我们的例子里面两个call的地址都一样,但是你如果写下代码 double ^ pDouble = gcnew double;这个时候的地址是多少?它和int 的一样么?

  目前我还没有仔细去研究这个地址到底对应的是该类型的MethodTable::Allocate()或是上面的这三个全局函数,如果对应MethodTable::Allocate(),那么2.0中应该有个MethodTable::FastAllocate()吧,否则应该就是对应的全局函数AllocateObject 以及FastAllocateObject了。过几天一定要抽空再好好研究一下。
 
  下面看对应的delete函数。
    delete pInt;
000000d8  mov         ecx,dword ptr [esp+18h]
000000dc  call        dword ptr ds:[03B51540h]
 
比较简单,就是传入地址,然后调用operator delete来释放类存,会调用析构函数
 
  对应的,释放gcnew创建的对象的代码如下:
    delete rInt;
000000ac  mov         edx,edi
000000ae  mov         ecx,788F747Ch
000000b3  call        FC8D20FD

  这个也相对简单,它对应vm里面的一个函数:
void  CallFinalizer(Thread* FinalizerThread, Object* fobj)

  那么也就是
fobjà edx
FinalizerThread à ecx
Call CallFinalizer
 
  但是,请注意!!!!!!!一个类包含析构函数和不包含析构函数,它对应的delete代码是不一样的,这点可以通过汇编代码比较得到,我这里就不多说了。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐椋庢閿熺姴鐭楅幖娣妼缁愭鏌¢崶鈺佷汗闁哄閰i弻鏇$疀鐎n亞浠炬繝娈垮灠閵堟悂寮婚弴锛勭杸閻庯綆浜栭崑鎾诲冀椤撱劎绋忛梺璺ㄥ櫐閹凤拷

    濠电姷鏁告慨鐑姐€傛禒瀣劦妞ゆ巻鍋撻柛鐔锋健閸┾偓妞ゆ巻鍋撶紓宥咃躬楠炲啫螣鐠囪尙绐為梺褰掑亰閸撴盯鎮惧ú顏呪拺闂傚牊鍗曢崼銉ョ柧婵犲﹤瀚崣蹇旂節婵犲倻澧涢柛瀣ㄥ妽閵囧嫰寮介妸褋鈧帡鏌熼挊澶婃殻闁哄瞼鍠栭幃婊堝煛閸屾稓褰嬮柣搴ゎ潐濞叉ê鐣濈粙璺ㄦ殾闁割偅娲栭悡娑㈡煕鐏炲墽鐭嬫繛鍫熸倐濮婄粯鎷呯粵瀣異闂佹悶鍔嬮崡鍐茬暦閵忋倕鍐€妞ゆ劑鍎卞皬闂備焦瀵х粙鎴犫偓姘煎弮瀹曚即宕卞Ο闀愮盎闂侀潧鐗嗛幊搴㈡叏椤掆偓閳规垿鍩ラ崱妞剧凹濠电姰鍨洪敋閾荤偞淇婇妶鍛櫤闁稿鍊圭换娑㈠幢濡纰嶉柣搴㈣壘椤︾敻寮诲鍫闂佸憡鎸鹃崰搴敋閿濆鏁嗗〒姘功閻绻涢幘鏉戠劰闁稿鎹囬弻锝呪槈濞嗘劕纾抽梺鍝勬湰缁嬫垿鍩為幋锕€宸濇い鏇炴噺閳诲﹦绱撻崒娆戝妽妞ゃ劌鎳橀幆宀勫磼閻愰潧绁﹂柟鍏肩暘閸斿矂鎮為崹顐犱簻闁圭儤鍨甸鈺呮倵濮橆剦妲归柕鍥у瀵粙濡歌閸c儳绱撴担绛嬪殭婵☆偅绻堝濠氭偄绾拌鲸鏅i悷婊冪Ч閹﹢鎳犻鍌滐紲闁哄鐗勯崝搴g不閻愮儤鐓涢悘鐐跺Г閸犳﹢鏌℃担鐟板鐎规洜鍠栭、姗€鎮╅搹顐ら拻闂傚倷娴囧畷鍨叏閹惰姤鈷旂€广儱顦崹鍌炴煢濡尨绱氶柨婵嗩槸缁€瀣亜閺嶃劎鈽夋繛鍫熺矒濮婅櫣娑甸崨顔俱€愬銈庡亝濞茬喖宕洪埀顒併亜閹哄棗浜鹃梺鎸庢穿婵″洤危閹版澘绫嶉柛顐g箘椤撴椽姊虹紒妯哄鐎殿噮鍓欒灃闁告侗鍠氶崢鎼佹⒑閸撴彃浜介柛瀣閹﹢鏁冮崒娑氬幈闁诲函缍嗛崑鍡樻櫠椤掑倻纾奸柛灞剧☉缁椦囨煙閻熸澘顏柟鐓庢贡閹叉挳宕熼棃娑欐珡闂傚倸鍊风粈渚€骞栭銈傚亾濮樺崬鍘寸€规洖缍婇弻鍡楊吋閸涱垽绱遍柣搴$畭閸庨亶藝娴兼潙纾跨€广儱顦伴悡鏇㈡煛閸ャ儱濡煎褜鍨伴湁闁绘ǹ绉鍫熺畳闂備焦瀵х换鍌毼涘Δ鍛厺闁哄洢鍨洪悡鍐喐濠婂牆绀堟慨妯挎硾閽冪喖鏌曟繛褍瀚烽崑銊╂⒑缂佹ê濮囨い鏇ㄥ弮閸┿垽寮撮姀鈥斥偓鐢告煥濠靛棗鈧懓鈻嶉崶銊d簻闊洦绋愰幉楣冩煛鐏炵偓绀嬬€规洟浜堕、姗€鎮㈡總澶夌处

    重磅专题
    往期文章
    最新文章